package suncertify.gui.dialogs;

import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import suncertify.application.Application;
import suncertify.application.ApplicationMode;
import suncertify.application.ClientConnectionDialogInterface;
import suncertify.application.Log;
import suncertify.application.Strings;
import suncertify.application.exceptions.DatabaseFileNotFoundException;
import suncertify.application.exceptions.DatabaseFileNotReadableException;
import suncertify.application.exceptions.DatabaseFileNotWritableException;
import suncertify.application.exceptions.DatabaseFileWrongTypeException;
import suncertify.application.exceptions.InvalidServerNameException;
import suncertify.gui.ActionCommands;
import suncertify.gui.DesktopGui;
import suncertify.gui.panels.FileLocationPanel;
import suncertify.gui.panels.NumericValuePanel;
import suncertify.gui.panels.ServerNamePanel;

/**
 * Provides a dialog that allows the user to locate and connect to the application's database. This dialog provides two
 * layouts depending on the {@link ApplicationMode} which is provided in the constructor.
 * 
 * <ol>
 * <li><b>Stand alone Layout</b>. This layout is used when the application mode is set to
 * {@link ApplicationMode#STANDALONE_CLIENT}, the dialog consists of the {@link FileLocationPanel} which allows the user
 * to enter the path of the database file on the local machine.</li>
 * <li><b>Network Layout</b>. This layout is used when the application mode is set to
 * {@link ApplicationMode#NETWORK_CLIENT}, the dialog consists of the {@link ServerNamePanel} and the
 * {@link NumericValuePanel} that allow the user to enter the computer name and the port of the computer running the
 * server application.</li>
 * 
 * <p>
 * In both layouts the dialog includes a submit and cancel buttons. The cancel button closes the dialog and nothing else
 * happens. The submit button validates the data and closes the dialog only if the data is valid.
 * 
 * <p>
 * The dialog is constructed with an implementation of the {@link ClientConnectionDialogInterface}, the dialog
 * represents the view part in a MVC pattern and the implementor of the interface represents the controller, the dialog
 * forwards all its user interactions to the implementor of the interface.
 * 
 * </ol>
 * 
 */
public class ClientConnectionDialog extends JEscapeKeyDialog implements ActionListener {

    /**
     * Automatically generated serialization version ID.
     */
    private static final long serialVersionUID = -5148084968775992171L;

    /**
     * Represents the client application's mode. Allowed values are {@link ApplicationMode#STANDALONE_CLIENT} and
     * {@link ApplicationMode#NETWORK_CLIENT}.
     */
    private ApplicationMode applicationMode;

    /**
     * Reference to the controller.
     */
    private ClientConnectionDialogInterface clientApplication;

    /**
     * Declares the submit button, this button starts the data validation.
     */
    private JButton openButton;

    /**
     * Declares the cancel button.
     */
    private JButton cancelButton;

    /**
     * Declares the file location panel.
     */
    private FileLocationPanel filePanel;

    /**
     * Declares the server name panel.
     */
    private ServerNamePanel serverNamePanel;

    /**
     * Declares the server port panel.
     */
    private NumericValuePanel serverPortPanel;

    /**
     * True if the cancel button is clicked, false otherwise.
     */
    private boolean isDialogCanceled = true;

    /**
     * Constructs the class with the specified parameters. The <code>appMode</code> and <code>clientApplication</code>
     * are stored in member variables. The dialogs root panel that defines the content pane is created, then depending
     * on the <code>appMode</code> either the {@link #buildDialogForStandAloneMode(JPanel)} or
     * {@link #buildDialogForNetworkMode(JPanel)} functions are called which provide the corresponding components.
     * 
     * <p>
     * The dialog is not resizable, is shown modal to the frame provided by <code>parentFrame</code> and located in it's
     * center. The dialog's title is set to either {@link Strings#CAP_REMOTE_SERVER} or
     * {@link Strings#CAP_DATABASE_FILE}.
     * 
     * @param appMode
     *            The application mode, allowed values are {@link ApplicationMode#STANDALONE_CLIENT} and
     *            {@link ApplicationMode#NETWORK_CLIENT}.
     * @param clientApplication
     *            An implementation of the {@link ClientConnectionDialogInterface} interface.
     * @param parentJFrame
     *            Reference to the client application's main frame.
     */
    public ClientConnectionDialog(ApplicationMode appMode, ClientConnectionDialogInterface clientApplication,
	    JFrame parentJFrame) {

	this.applicationMode = appMode;

	assert (applicationMode == ApplicationMode.NETWORK_CLIENT || applicationMode == ApplicationMode.STANDALONE_CLIENT) : "ServerApplication mode is not allowed for the ClientDialog!";

	this.clientApplication = clientApplication;

	JPanel rootPanel = new JPanel();
	rootPanel.setLayout(new BorderLayout());

	if (applicationMode == ApplicationMode.NETWORK_CLIENT) {
	    this.setTitle(Strings.CAP_REMOTE_SERVER);
	    this.buildDialogForNetworkMode(rootPanel);
	} else {
	    this.setTitle(Strings.CAP_DATABASE_FILE);
	    this.buildDialogForStandAloneMode(rootPanel);
	}

	this.setContentPane(rootPanel);
	this.setModal(true);
	this.setResizable(false);
	this.pack();
	this.setLocationRelativeTo(parentJFrame);
	this.getRootPane().setDefaultButton(this.openButton);
    }

    /**
     * Creates the {@link FileLocationPanel} and the buttons panel by calling {@link #createButtonPanel()} and appends
     * both panels to the dialogs root panel.
     * 
     * @param rootPanel
     *            The dialogs root panel.
     */
    private void buildDialogForStandAloneMode(JPanel rootPanel) {

	this.filePanel = new FileLocationPanel(clientApplication.getDatabaseFilePath(), this.clientApplication
		.getUserCurrentDirectory());

	JPanel buttonsPanel = this.createButtonPanel();

	rootPanel.add(this.filePanel, BorderLayout.NORTH);
	rootPanel.add(buttonsPanel, BorderLayout.SOUTH);
    }

    /**
     * Creates the {@link ServerNamePanel}, the {@link NumericValuePanel} and the buttons panel by calling
     * {@link #createButtonPanel()} and adds them to the dialogs root panel.
     * 
     * @param rootPanel
     *            The dialogs root panel.
     */
    private void buildDialogForNetworkMode(JPanel rootPanel) {

	this.serverNamePanel = new ServerNamePanel(this.clientApplication.getServerName());
	this.serverPortPanel = new NumericValuePanel(this.clientApplication.getPortNumber(), Strings.CAP_PORT,
		Application.MAXIMUM_PORT_VALUE, false, KeyEvent.VK_P, Strings.TIP_PORT_CLIENT);

	JPanel serverPanel = new JPanel(new BorderLayout());

	serverPanel.add(serverNamePanel, BorderLayout.NORTH);
	serverPanel.add(serverPortPanel, BorderLayout.SOUTH);

	JPanel buttonsPanel = this.createButtonPanel();

	rootPanel.add(serverPanel, BorderLayout.NORTH);
	rootPanel.add(buttonsPanel, BorderLayout.SOUTH);
    }

    /**
     * Creates a panel that contains the submit and cancel buttons, the buttons are aligned to the right in the panel.
     * The cancel button's action command is set to {@link ActionCommands#CANCEL}. Depending on the application's mode
     * defined in the constructor, the submit button's action command is set to either {@link ActionCommands#OPEN_FILE}
     * or {@link ActionCommands#CONNECT_TO_SERVER}, the submit's button text is also set similarly to either
     * {@link Strings#CAP_OPEN_FILE} or {@link Strings#CAP_CONNECT}.
     * 
     * @return Panel with the submit and cancel buttons.
     */
    private JPanel createButtonPanel() {

	this.openButton = new JButton();

	if (this.applicationMode == ApplicationMode.STANDALONE_CLIENT) {
	    this.openButton.setText(Strings.CAP_OPEN_FILE);
	    this.openButton.setActionCommand(ActionCommands.OPEN_FILE.toString());
	    this.openButton.setMnemonic(KeyEvent.VK_O);

	} else {
	    this.openButton.setText(Strings.CAP_CONNECT);
	    this.openButton.setActionCommand(ActionCommands.CONNECT_TO_SERVER.toString());
	    this.openButton.setMnemonic(KeyEvent.VK_T);
	}

	this.openButton.addActionListener(this);

	this.cancelButton = new JButton(Strings.CAP_CANCEL);
	this.cancelButton.addActionListener(this);
	this.cancelButton.setActionCommand(ActionCommands.CANCEL.toString());
	this.cancelButton.setMnemonic(KeyEvent.VK_C);

	JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
	buttonPanel.add(this.openButton);
	buttonPanel.add(this.cancelButton);

	return buttonPanel;
    }

    /**
     * Called by swing when the {@link #openButton} or the {@link #cancelButton} are clicked. The clicked buttons are
     * identified by the {@link ActionEvent} parameter that contains the {@link ActionCommands} enumeration assigned in
     * {@link #createButtonPanel()}.
     * 
     * <p>
     * If the {@link #openButton} is clicked while the application runs in {@link ApplicationMode#STANDALONE_CLIENT}
     * mode then the file path of the database is validated, if the file does not exist or cannot be accesses for read
     * or write then an error message dialog is shown, if no error occurs then the
     * {@link ClientConnectionDialogInterface#createContractorsAccess(String)} function is called which provides access
     * to the local database and the dialog is set to invisible.
     * 
     * <p>
     * If the {@link #openButton} is clicked while the application runs in {@link ApplicationMode#NETWORK_CLIENT} mode
     * then the computer name and port of the server are validated, if both values are correct then the
     * {@link ClientConnectionDialogInterface#createRemoteContractorsAccess(String, int)} is called which provides
     * remote access to the database then the dialog is set to invisible, if the values are not correct then an error
     * message dialog is shown.
     * 
     * <p>
     * If the cancel button is clicked then the dialog is set to invisible and nothing else happens.
     * 
     * @param e
     *            See {@link ActionEvent}
     * 
     */
    public void actionPerformed(ActionEvent e) {

	try {
	    this.isDialogCanceled = true;

	    if (e.getActionCommand().equalsIgnoreCase(ActionCommands.OPEN_FILE.toString())) {

		this.validateOpenFileCommand();

		String filePath = this.filePanel.getDatabaseFilePath();

		if (!this.clientApplication.createContractorsAccess(filePath)) {
		    return;
		}

		this.isDialogCanceled = false;
		this.closeDialogCommand();

	    } else if (e.getActionCommand().equalsIgnoreCase(ActionCommands.CONNECT_TO_SERVER.toString())) {

		String serverName = this.serverNamePanel.getServerName();
		this.clientApplication.isServerNameValid(serverName);

		if (this.serverPortPanel.isEmpty()) {
		    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_INVALID_PORT_NUMBER);
		    return;
		}

		int portNumber = this.serverPortPanel.getValue();

		if (!this.clientApplication.isPortNumberValid(portNumber)) {
		    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_INVALID_PORT_NUMBER);
		    return;
		}

		if (!this.clientApplication.createRemoteContractorsAccess(serverName, this.serverPortPanel.getValue())) {
		    return;
		}

		this.isDialogCanceled = false;
		this.closeDialogCommand();

	    } else {
		this.closeDialogCommand();
		this.isDialogCanceled = true;
	    }
	} catch (InvalidServerNameException serverNameEx) {
	    Log.logInfo("ClientConnectionDialog.actionPerfomred() caught InvalidServerNameException");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_INVALID_SERVER_NAME);

	} catch (DatabaseFileNotFoundException ex) {
	    Log.logInfo("ClientConnectionDialog.actionPerfomred() caught DatabaseFileNotFoundException");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_DATABASE_FILE_NOT_FOUND);

	} catch (DatabaseFileWrongTypeException ex) {
	    Log.logInfo("ClientConnectionDialog.actionPerfomred() caught DatabaseFileWrongTypeException");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_DATABASE_FILE_WRONG_TYPE);

	} catch (DatabaseFileNotReadableException ex) {
	    Log.logInfo("ClientConnectionDialog.actionPerfomred() caught DatabaseFileNotReadableException");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_DATABASE_FILE_READ_ERROR);

	} catch (DatabaseFileNotWritableException ex) {
	    Log.logInfo("ClientConnectionDialog.actionPerfomred() caught DatabaseFileNotWritableException");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_DATABASE_FILE_WRITE_ERROR);

	} catch (Exception ex) {

	    if (ex.getMessage() != null) {
		Log.logInfo("ClientConnectionDialog.actionPerfomred() caught Exception with message: "
			+ ex.getMessage());
	    } else {
		Log.logInfo("ClientConnectionDialog.actionPerfomred() caught Exception without message.");
	    }

	    DesktopGui.showPerformedActionErrorMessage(ex);
	}
    }

    /**
     * Validates the database file path by passing it to the
     * {@link ClientConnectionDialogInterface#isDatabaseFileValid(String)}, if no exception is thrown then the path is
     * considered to be valid.
     * 
     * @throws DatabaseFileNotFoundException
     *             Thrown if the file does not exist in the local file system.
     * @throws DatabaseFileWrongTypeException
     *             Thrown if the file path ends with the wrong file extension.
     * @throws DatabaseFileNotReadableException
     *             Thrown if the file is not readable.
     * @throws DatabaseFileNotWritableException
     *             Thrown if the file is not writable.
     */
    private void validateOpenFileCommand() throws DatabaseFileNotFoundException, DatabaseFileWrongTypeException,
	    DatabaseFileNotReadableException, DatabaseFileNotWritableException {

	String filePath = this.filePanel.getDatabaseFilePath();

	this.clientApplication.isDatabaseFileValid(filePath);
    }

    /**
     * Closes the dialog by calling {@link Dialog#setVisible(boolean)}.
     */
    private void closeDialogCommand() {

	this.setVisible(false);
	this.dispose();
    }

    /**
     * Determines if the dialog was closed by clicking the cancel button.
     * 
     * @return True if the dialog was closed by clicking the cancel button, false otherwise.
     */
    public boolean isDialogCanceled() {
	return this.isDialogCanceled;
    }
}
